@file:Suppress("DEPRECATION")

package com.utils

import com.Main
import com.entity.PackPath
import com.entity.PathUnionType
import com.entity.Script
import com.script.KotlinScriptEngine
import com.script.KotlinScriptEngineFactory
import com.script.ScriptContextImpl
import com.subpackage.ContrastFile
import extend.error
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.apache.logging.log4j.kotlin.logger
import org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineBase
import org.slf4j.LoggerFactory
import java.io.File
import java.io.FileReader
import java.io.UnsupportedEncodingException
import java.net.URLDecoder
import java.util.*
import javax.script.Bindings
import javax.script.ScriptContext
import javax.script.ScriptEngineManager
import kotlin.io.path.extension
import kotlin.io.path.name
import kotlin.system.measureTimeMillis

object ScriptUtils {

    val scriptEngine by lazy {
//        val manager = ScriptEngineManager()
        val factory = KotlinScriptEngineFactory()
//        val engine = manager.getEngineByExtension("kts") as KotlinJsr223ScriptEngineImpl
        val engine = factory.scriptEngine as KotlinScriptEngine

        val time = measureTimeMillis {
            engine.eval("true")
        }

        engine.context = ScriptContextImpl()

        logger.debug("测试执行时间: $time ms")
        engine
    }

    val appConfig by lazy {
        YamlUtil.getAppConfig()
    }

    /**
     * 脚本的基础放置路径
     */
    var basePath = "data/script/"

    val script by lazy {
        Script {
            val list = mutableListOf<PackPath>()
            it.forEach { value ->
                when (value) {
                    is PathUnionType.StringType -> list.add(PackPath(value.value))
                    is PathUnionType.PathType -> list.add(value.value)
                }
            }
            loadFile(*list.toTypedArray())
        }
    }

    val contrastFile by lazy {
        ContrastFile()
    }

    fun readJs(url: String): Bindings {
        /*获取执行JavaScript的执行引擎*/
        val engine = ScriptEngineManager().getEngineByName("javascript")
        /*为文件注入全局变量*/
        val bindings = engine.createBindings()
        /*设置绑定参数的作用域*/
        engine.setBindings(bindings, ScriptContext.ENGINE_SCOPE)
        engine.eval(FileReader(url))
        return bindings
    }

    fun initScript() {
        val scriptMap = mutableSetOf<PackPath>()

        val scriptFiles = PackPath(basePath).walk()
        scriptFiles.forEach {
            if (it.extension == "kts" && it.name.startsWith("Main", true)) {
                scriptMap.add(PackPath(it))
            }
        }
        logger.info("加载主脚本：${scriptMap.size} 个")

        scriptEngine.put("isWindow", isWindow)
        scriptEngine.put("rootPath", rootPath)
        scriptEngine.put("packScript", script)
        scriptEngine.put("contrastFile", contrastFile)
        scriptEngine.
        logger.info("设置脚本全局属性完成")

        runCatching {
            loadFile(*scriptMap.toTypedArray())
            logger.debug("load all script complete ")
        }.onFailure {
            logger.error(it.message, it)
            throw it
        }
    }

    fun loadFile(vararg path: String) {
        val paths = path.mapTo(mutableListOf()) {
            PackPath(it)
        }.toTypedArray()
        loadFile(*paths)
    }

    fun loadFile(vararg paths: PackPath) {
        logger.info("加载脚本 ********* ${paths.joinToString { it.nameWithoutExtension }}")
        val compiledMap = Collections.synchronizedMap<String, KotlinJsr223JvmScriptEngineBase.CompiledKotlinScript>(linkedMapOf())
        val runTime = measureTimeMillis {
            runBlocking {
                for (path in paths) {
                    launch(Dispatchers.IO) {
                        logger.info("加载脚本 $path")
                        val scripts = path.readText()
                        logger.info("加载脚本文件大小 ${scripts.length} kb")
                        if (scripts.isNotBlank()) {
                            val name = path.nameWithoutExtension
                            runScript(name, scripts).let {
                                compiledMap[name] = it
                            }
                        }
                    }
                }
            }
            logger.info("eval compiled files=${paths.joinToString { it.nameWithoutExtension }}")
            println("compiled map keys = ${compiledMap.keys.joinToString()}")
            compiledMap.forEach { (key, value) ->
                scriptEngine.eval(value, scriptEngine.context)
                logger.info("run script: name=$key -> ${value.compiledData.mainClassName}")
            }
        }
        logger.info("本次加载脚本${paths.size}个, 共耗时 $runTime ms, 详细文件: [${paths.joinToString { it.nameWithoutExtension }}]")
    }

    fun runScript(name: String, script: String): KotlinJsr223JvmScriptEngineBase.CompiledKotlinScript {
        logger.info("compile script: name=$name")
        val context = ScriptContextImpl()
        val compileScript = scriptEngine.compile(script, context, name)
        return compileScript
    }

    val isWindow by lazy { isWin() }

    val rootPath: String by lazy { runPath.path }

    val runPath by lazy {
        var file: File
        val jar = Main::class.java.getResource("Main.class")
        if (jar?.protocol == "jar") {
            val url = Main::class.java.getProtectionDomain().codeSource.location
            try {
                var filePath = URLDecoder.decode(url.path, "UTF-8")
                if (filePath.endsWith(".jar")) { // 可执行jar包运行的结果里包含".jar"
                    // 截取路径中的jar包名
                    filePath = filePath.take(filePath.lastIndexOf("/") + 1)
                }
                file = File(filePath)
            } catch (e: UnsupportedEncodingException) {
                LoggerFactory.getLogger(Main::class.java).error(e.message)
                file = File(System.getProperty("user.dir"))
            }
        } else {
            file = File(System.getProperty("user.dir"))
        }
        file
    }

    fun isWin(): Boolean {
        return System.getProperty("os.name").startsWith("Win", true)
    }

}